﻿#if !SILVERLIGHT
// ===================================
// <copyright>LogoUI Co.</copyright>
// <author>Vlad Spivak</author>
// <email>mailto:vlads@logoui.co.il</email>
// <created>25/00/11</created>
// <lastedit>25/00/11</lastedit>

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//
// <remarks>Part of this software based on various internet sources, mostly on the works
// of members of Wpf Disciples group http://wpfdisciples.wordpress.com/
// Also project may contain code from the frameworks: 
//        Nito - http://nitokitchensink.codeplex.com/
//        OpenLightGroup - http://openlightgroup.net/
//        nRoute - http://nroute.codeplex.com/
//        SilverlightFX - http://projects.nikhilk.net/SilverlightFX
//        BlackLight - http://blacklight.codeplex.com/
// </remarks>
// ====================================================================================//

//this code is courtesy of http://blindmeis.wordpress.com/2010/06/01/wpf-masked-textbox-behavior/

using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;

namespace LogoFX.Infra.Interactivity
{
    /// <summary>
    /// InputMask for Textbox with 2 Properties: <see cref="InputMask"/>, <see cref="PromptChar"/>.
    /// </summary>
    public class TextBoxInputMaskBehavior : Behavior<TextBox>
    {
        #region DependencyProperties

        private string _maskInternal;
        private const string IP_MASK = "999.999.999.999";
        private ReentranceGuard _lock = new ReentranceGuard();

        public static readonly DependencyProperty InputMaskProperty =
          DependencyProperty.Register("InputMask", typeof(string), typeof(TextBoxInputMaskBehavior), new PropertyMetadata(OnMaskChanged));

        private static void OnMaskChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            TextBoxInputMaskBehavior tbmib = (TextBoxInputMaskBehavior)d;
            if (!tbmib.IsIpMode)
            {
                tbmib._maskInternal = ((string)e.NewValue);
            }
        }

        public string InputMask
        {
            get { return (string)GetValue(InputMaskProperty); }
            set { SetValue(InputMaskProperty, value); }
        }

        public static readonly DependencyProperty PromptCharProperty =
           DependencyProperty.Register("PromptChar", typeof(char), typeof(TextBoxInputMaskBehavior),
                                        new PropertyMetadata('_'));

        public char PromptChar
        {
            get { return (char)GetValue(PromptCharProperty); }
            set { SetValue(PromptCharProperty, value); }
        }

        #endregion

        public MaskedTextProvider Provider { get; private set; }

        private bool _isIpMode = false;
        public bool IsIpMode
        {
            get { return _isIpMode; }
            set
            {
                _isIpMode = value;
                if (_isIpMode)
                {
                    _maskInternal = IP_MASK;
                }
            }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Loaded += AssociatedObjectLoaded;
            AssociatedObject.PreviewTextInput += AssociatedObjectPreviewTextInput;
            AssociatedObject.PreviewKeyDown += AssociatedObjectPreviewKeyDown;
            AssociatedObject.LostFocus += AssociatedObjectLostFocus;
            AssociatedObject.TextChanged += AssociatedObjectTextChanged;

            DataObject.AddPastingHandler(AssociatedObject, Pasting);
        }

        private void AssociatedObjectTextChanged(object sender, TextChangedEventArgs e)
        {
            using (_lock.Raise())
            {
                if (!_lock.IsLocked)
                {
                    UpdateText();
                }
            }
        }


        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.Loaded -= AssociatedObjectLoaded;
            AssociatedObject.PreviewTextInput -= AssociatedObjectPreviewTextInput;
            AssociatedObject.PreviewKeyDown -= AssociatedObjectPreviewKeyDown;
            AssociatedObject.LostFocus -= AssociatedObjectLostFocus;
            AssociatedObject.TextChanged -= AssociatedObjectTextChanged;
            DataObject.RemovePastingHandler(AssociatedObject, Pasting);
        }

        private void AssociatedObjectLostFocus(object sender, RoutedEventArgs e)
        {
            RefreshText(200);
        }

        /*
        // Mask Character  Accepts  Required?  
        // 0  Digit (0-9)  Required  
        // 9  Digit (0-9) or space  Optional  
        // #  Digit (0-9) or space  Required  
        // L  Letter (a-z, A-Z)  Required  
        // ?  Letter (a-z, A-Z)  Optional  
        // &  Any character  Required  
        // C  Any character  Optional  
        // A  Alphanumeric (0-9, a-z, A-Z)  Required  
        // a  Alphanumeric (0-9, a-z, A-Z)  Optional  
        //    Space separator  Required 
        // .  Decimal separator  Required  
        // ,  Group (thousands) separator  Required  
        // :  Time separator  Required  
        // /  Date separator  Required  
        // $  Currency symbol  Required  

        In addition, the following characters have special meaning:

        Mask Character  Meaning  
        <  All subsequent characters are converted to lower case  
        >  All subsequent characters are converted to upper case  
        |  Terminates a previous < or >  
        \  Escape: treat the next character in the mask as literal text rather than a mask symbol  

        */

        void AssociatedObjectLoaded(object sender, System.Windows.RoutedEventArgs e)
        {
            this.Provider = new MaskedTextProvider(_maskInternal, CultureInfo.CurrentCulture);
            this.Provider.PromptChar = this.PromptChar;
            UpdateText();
            //seems the only way that the text is formatted correct, when source is updated
            var textProp = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
            if (textProp != null)
            {
                textProp.AddValueChanged(AssociatedObject, (s, args) => this.UpdateText());
            }
        }

        void AssociatedObjectPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
        {
            this.TreatSelectedText();

            var position = this.GetNextCharacterPosition(AssociatedObject.SelectionStart);

            if (Keyboard.IsKeyToggled(Key.Insert) || IsIpMode)
            {
                if (this.Provider.Replace(e.Text, position))
                    position++;
            }
            else
            {
                if (this.Provider.InsertAt(e.Text, position))
                    position++;
            }

            position = this.GetNextCharacterPosition(position);

            this.RefreshText(position);

            e.Handled = true;
        }

        void AssociatedObjectPreviewKeyDown(object sender, KeyEventArgs e)
        {

            if (e.Key == Key.Space)//handle the space
            {
                this.TreatSelectedText();

                var position = this.GetNextCharacterPosition(AssociatedObject.SelectionStart);

                if (this.Provider.InsertAt(" ", position))
                    this.RefreshText(position);

                e.Handled = true;
            }

            if (e.Key == Key.Back)//handle the back space
            {
                if (this.TreatSelectedText())
                {
                    this.RefreshText(AssociatedObject.SelectionStart);
                }
                else
                {
                    if (AssociatedObject.SelectionStart != 0)
                    {
                        if (this.Provider.RemoveAt(AssociatedObject.SelectionStart - 1))
                            this.RefreshText(AssociatedObject.SelectionStart - 1);
                    }
                }

                e.Handled = true;
            }

            if (e.Key == Key.Delete)//handle the delete key
            {
                //treat selected text
                if (this.TreatSelectedText())
                {
                    this.RefreshText(AssociatedObject.SelectionStart);
                }
                else
                {

                    if (this.Provider.RemoveAt(AssociatedObject.SelectionStart))
                        this.RefreshText(AssociatedObject.SelectionStart);

                }

                e.Handled = true;
            }

        }

        /// <summary>
        /// Pasting prüft ob korrekte Daten reingepastet werden
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Pasting(object sender, DataObjectPastingEventArgs e)
        {
            if (e.DataObject.GetDataPresent(typeof(string)))
            {
                var pastedText = (string)e.DataObject.GetData(typeof(string));

                this.TreatSelectedText();

                var position = GetNextCharacterPosition(AssociatedObject.SelectionStart);

                if (this.Provider.InsertAt(pastedText, position))
                {
                    this.RefreshText(position);
                }
            }

            e.CancelCommand();
        }

        private string TargetText
        {
            get
            {
                return this.Provider.ToDisplayString(); 
            }
        }
        private void UpdateText()
        {
            if (this.Provider == null || TargetText.Equals(AssociatedObject.Text))
                return;

            bool success;
            
            if (IsIpMode)
            {
                
                string s = "";
                string[] tokens = AssociatedObject.Text.Split('.');
                foreach (string token in tokens)
                {
                    
                    byte b;
                    if (!Byte.TryParse(token.Trim(), out b))
                    {
                        b = 0;
                    }
                    s += b.ToString().PadLeft(3);
                    s += ".";
                }
                s = s.TrimEnd('.');
                success = Provider.Set(s);
            }
            else
            {
                success = this.Provider.Set(AssociatedObject.Text);
            }


            //ui and mvvm/codebehind should be in sync
            this.SetText(success ? TargetText : AssociatedObject.Text);
        }

        /// <summary>
        /// Falls eine Textauswahl vorliegt wird diese entsprechend behandelt.
        /// </summary>
        /// <returns>true Textauswahl behandelt wurde, ansonsten falls </returns>
        private bool TreatSelectedText()
        {
            if (AssociatedObject.SelectionLength > 0)
            {
               return this.Provider.RemoveAt(AssociatedObject.SelectionStart, 
                                              AssociatedObject.SelectionStart + AssociatedObject.SelectionLength - 1);
            }
            return false;
        }

        private void RefreshText(int position)
        {
            if (IsIpMode)
            {
                string s = "";
                string[] tokens = TargetText.Split('.');
                for (int j = 0; j < 4; j++)
                {
                    if (position < (j + 1) * 4 && position >= j * 4)
                    {
                        s += tokens[j];
                    }
                    else
                    {
                        byte b;
                        if (!Byte.TryParse(tokens[j].Trim(), out b))
                        {
                            b = 0;
                        }
                        s += b.ToString().PadLeft(3);
                    }
                    s += ".";
                }
                s = s.TrimEnd('.');
                MaskedTextResultHint mh;
                int pos;
                Provider.Set(s, out pos, out mh);
            }
            SetText(TargetText);
            AssociatedObject.SelectionStart = position;
        }

        private void SetText(string text)
        {
            using (_lock.Raise())
            {
                if(!_lock.IsLocked)
                    AssociatedObject.Text = String.IsNullOrWhiteSpace(text) ? String.Empty : text;
            }
        }

        private int GetNextCharacterPosition(int startPosition)
        {
            var position = this.Provider.FindEditPositionFrom(startPosition, true);

            if (position == -1)
                return startPosition;
            else
                return position;
        }
    }
}
#endif